/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Threading;

namespace DCSharp
{
	// FIXME: Rename since it's not really a queue.
	/// <summary>
	/// Invokes methods, one at the time, on a separate thread. All methods can
	/// be aborted.
	/// </summary>
	/// <remarks>
	/// The enqueued methods should abort if the <see cref="ManualResetEvent"/>
	/// from the AbortEvent property has been set.
	/// </remarks>
	public class ActionQueue<T> where T : class
	{
		private Dictionary<T, Handler> workItems;
		private T active;

		private Thread thread;
		private ManualResetEvent abortEvent;
		private string threadName;

		/// <summary>
		/// Initializes a new ActionQueue instance.
		/// </summary>
		public ActionQueue() : this(null)
		{
		}

		/// <summary>
		/// Initializes a new ActionQueue instance.
		/// </summary>
		/// <param name="threadName">The name to use for the new thread.</param>
		public ActionQueue(string threadName)
		{
			this.threadName = threadName;

			workItems = new Dictionary<T, Handler>();
			abortEvent = new ManualResetEvent(false);
		}

		#region Classes

		private class Handler
		{
			private WaitCallback callback;
			private object state;

			public Handler(WaitCallback callback, object state)
			{
				this.callback = callback;
				this.state = state;
			}

			public void Invoke()
			{
				callback(state);
			}
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets whether or not a method is running.
		/// </summary>
		/// <value>Whether or not a method is running.</value>
		public bool IsRunning
		{
			get { return thread != null; }
		}

		/// <summary>
		/// Gets the key of the active method.
		/// </summary>
		/// <value>The key of the active method.</value>
		public T Active
		{
			get { return active; }
		}

		/// <summary>
		/// Gets the event that tells if the active method should be stopped or not.
		/// </summary>
		/// <value>The event that tells if the active method should be stopped or not.</value>
		public ManualResetEvent AbortEvent
		{
			get { return abortEvent; }
		}

		public object SyncRoot
		{
			get { return workItems; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Queues a method for execution.
		/// </summary>
		/// <param name="key">The identifier for the method.</param>
		/// <param name="callback">The method to invoke.</param>
		public void Queue(T key, WaitCallback callback)
		{
			Queue(key, callback, null);
		}

		/// <summary>
		/// Queues a method for execution.
		/// </summary>
		/// <param name="key">The identifier for the method.</param>
		/// <param name="callback">The method to invoke.</param>
		/// <param name="state">The object to pass to the method.</param>
		public void Queue(T key, WaitCallback callback, object state)
		{
			if (callback == null)
			{
				throw new ArgumentNullException("callback");
			}
			lock (workItems)
			{
				if (!workItems.ContainsKey(key))
				{
					workItems.Add(key, new Handler(callback, state));
					if (thread == null)
					{
						thread = new Thread(Start);
						thread.Name = threadName;
						thread.IsBackground = true;
						thread.Start();
					}
				}
			}
		}

		/// <summary>
		/// Checks if a method is queued for execution.
		/// </summary>
		/// <param name="key">The identifier for the method.</param>
		/// <returns>True if the method was found; otherwise, false.</returns>
		public bool Contains(T key)
		{
			lock (workItems)
			{
				return workItems.ContainsKey(key);
			}
		}

		/// <summary>
		/// Removes a method from the queue or aborts the method if it's running.
		/// </summary>
		/// <param name="key">The identifier for the method.</param>
		public void Abort(T key)
		{
			lock (workItems)
			{
				if (workItems.ContainsKey(key))
				{
					workItems.Remove(key);
				}
				if (key.Equals(active))
				{
					abortEvent.Set();
				}
			}
		}

		private void Start()
		{
			while (true)
			{
				Handler handler;
				lock (workItems)
				{
					IEnumerator<KeyValuePair<T, Handler>> enumerator =
						workItems.GetEnumerator();

					if (enumerator.MoveNext())
					{
						active = enumerator.Current.Key;
						handler = enumerator.Current.Value;

						workItems.Remove(active);
					}
					else
					{
						thread = null;
						break;
					}
					abortEvent.Reset();
				}

				try
				{
					handler.Invoke();
				}
				finally
				{
					active = null;
				}
			}
		}

		#endregion
	}
}
